package botlab;

import botlab.command.BaseCommand;
import botlab.control.CommandControl;
import com.jme3.asset.AssetManager;
import com.jme3.bounding.BoundingBox;
import com.jme3.bullet.BulletAppState;
import com.jme3.bullet.collision.PhysicsCollisionListener;
import com.jme3.bullet.collision.shapes.CollisionShape;
import com.jme3.bullet.control.GhostControl;
import com.jme3.bullet.control.RigidBodyControl;
import com.jme3.bullet.util.CollisionShapeFactory;
import com.jme3.math.ColorRGBA;
import com.jme3.math.Vector3f;
import com.jme3.scene.Node;
import com.jme3.scene.Spatial;
import com.jme3.scene.control.Control;
import java.util.Iterator;
import java.util.LinkedList;

/**
 *
 * @author Quentin
 */
public final class Entity
{

    public static enum PhysicsType{
        GHOST,STATIC,KINEMATIC,DYNAMIC;
    };
    
    protected Game app = Game.getApp();
    protected BulletAppState bulletAppState = app.getStateManager().getState(
            BulletAppState.class);
    protected AssetManager assetManager = app.getAssetManager();
    
    protected Spatial spatial;
    protected Node node;
    protected Node debugNode;
    protected CollisionShape collisionShape;
    protected PhysicsType type;
    protected boolean rayCastable=true;
    
    protected LinkedList<Control> controls;
    protected RigidBodyControl rigidBodyControl;
    protected GhostControl ghostControl;
    
    

    public Entity(String path, float mass, Vector3f position, float scale, PhysicsType type)
    {
        controls=new LinkedList<Control>();
        rigidBodyControl=null;
        spatial = assetManager.loadModel(path);
        spatial.setLocalScale(scale);
        node = (Node) spatial;
        this.type=type;
        
        setMass(mass);
        setPhysicType(type);
        
        switch(type)
        {
            case GHOST:
                setMass(0.f);
                collisionShape = CollisionShapeFactory.createMeshShape(spatial);
                addGhostControl();
                spatial.setLocalTranslation(position);
                break;
            case STATIC:
                setMass(0.f);
                collisionShape = CollisionShapeFactory.createMeshShape(spatial);
                addPhysicsControl();
                spatial.getControl(RigidBodyControl.class).setPhysicsLocation(position);
                break;
            case KINEMATIC:
                collisionShape = CollisionShapeFactory.createMeshShape(spatial);
                addPhysicsControl();
                spatial.getControl(RigidBodyControl.class).setKinematic(true);
                spatial.setLocalTranslation(position);
                break;
            case DYNAMIC:
                collisionShape = CollisionShapeFactory.createDynamicMeshShape(spatial);
                addPhysicsControl();
                spatial.getControl(RigidBodyControl.class).setPhysicsLocation(position);
                break;
            default:
                break;
        }
        
        /*
         * collisionShape = CollisionShapeFactory.createMeshShape(spatial);
         * collisionShape = CollisionShapeFactory.createBoxShape(spatial);
         * collisionShape = CollisionShapeFactory.createDynamicMeshShape(spatial); //hull shape
         */
    }
    
    public void initDebug()
    {
        if(debugNode==null)
        {
            debugNode=new Node();
        }
    }
    
    public void setDebugOrientationOn()
    {
        initDebug();
        Utils.createArrow(ColorRGBA.Blue, Vector3f.UNIT_Z,debugNode);
        Utils.createArrow(ColorRGBA.Green, Vector3f.UNIT_Y,debugNode);
        Utils.createArrow(ColorRGBA.Red, Vector3f.UNIT_X,debugNode);
        node.attachChild(debugNode);
    }
    
    public void setDebugOrientationOff()
    {
        node.detachChild(debugNode);
    }
    
    public boolean isRayCastable()
    {
        return rayCastable;
    }
    
    public void setRayCastable(boolean b)
    {
        this.rayCastable=b;
    }
    
    public float getMass()
    {
        return (Float) spatial.getUserData("mass");
    }

    public final void setMass(float mass)
    {
        spatial.setUserData("mass", mass);
    }
    
    public void setPhysicType(PhysicsType type)
    {
        spatial.setUserData("physic_type",type.toString());
    }

    public CollisionShape getCollisionShape()
    {
        return this.collisionShape;
    }

    public void setCollisionShape(CollisionShape shape)
    {
        this.collisionShape = shape;
    }

    private void addPhysicsControl()
    {
        if(rigidBodyControl==null)
        {
            rigidBodyControl = new RigidBodyControl(
                getCollisionShape(), getMass());
            rigidBodyControl.setLinearVelocity(Vector3f.ZERO);
            addControl(rigidBodyControl);
            bulletAppState.getPhysicsSpace().add(rigidBodyControl);
        }
        
    }
    
    public void addCommandControl(BaseCommand command)
    {
        CommandControl control=new CommandControl();
//        control.setEnabled(false);
        control.setCommand(command);
        addControl(control);
    }
    
    public void addControl(Control c)
    {
//        if( c instanceof BaseControl)
//            ((BaseControl)c).setEnabled(false);
        controls.push(c);
        spatial.addControl(c);
    }
    
    /*
     * add a ghost control that detect physics collisions
     * and report them to all physicsCollisionListener
     */
    public void addGhostControl()
    {
        if(collisionShape != null)
        {
            ghostControl = new GhostControl(collisionShape);
            // attach the control to the spatial
            spatial.addControl(ghostControl);
            // and adding it to the physic space
            bulletAppState.getPhysicsSpace().add(ghostControl);
        }
    }
    
    /*
     * Remove all controls from spatial and from physics space.
     */
    public void cleanup()
    {
        // We remove the rigid body from physic space if any
        if(rigidBodyControl!=null)
            bulletAppState.getPhysicsSpace().remove(rigidBodyControl);
        
        // We remove the ghost control from physic space if any
        if(ghostControl!=null)
            bulletAppState.getPhysicsSpace().remove(ghostControl);
        
        // We remove all controls attached to the spatial
        for(Iterator<Control> it = controls.iterator();it.hasNext();)
        {
            Control c = it.next();
            // If the control is a collision listener, we have to remove it from physics space too.
            if(c instanceof PhysicsCollisionListener)
                bulletAppState.getPhysicsSpace().removeCollisionListener((PhysicsCollisionListener)c);
            spatial.removeControl(c);
        }
    }

    public Spatial getSpatial()
    {
        return spatial;
    }

    public void setSpatial(Spatial spatial)
    {
        this.spatial = spatial;
    }

    public Node getNode()
    {
        return node;
    }

    public void setNode(Node node)
    {
        this.node = node;
    }

    protected Vector3f getExtents()
    {
        return ((BoundingBox) spatial.getWorldBound()).getExtent(null);
    }
}
